예외 안전성(Exception Safety)아래 코드는 예외안전성이 갖추어 지지 않음 소스 코드임
class PrettyMenu{
public:
void changeBackground(std::istream& imgSrc);
private:
Mutex mutex;
Image* bgImage;
int imageChanges;
};
void PrettyMenu::changeBackground(std::istream& imgSrc){
lock(&mutex);
delete bgImage;
++imageChanges;
bgImage=new Image(imgSrc);
unlock(&mutex);
}
예외 안전성
- 자원이 새지 않도록 만든다.
위의 코드에서 lock(&mutex) 자원을 소모하고, new에서 예외가 발생 시, 사용한 자원을 해제하지 못한다.
- 자료구조가 더렵혀지지 않도록 구현한다.
위의 코드에서 new에서 예외가 발생 시,
이미 bgImage객체는 삭제되었고, imageChange도 변경되어 있다.
void prettyMenu::changeBackground(std::istream& imgSrc){
Lock ml(&mutex);
delete bgImage;
++imageChanges;
bgImage=newImage(imgSrc);
}
위와 같이 객체를 사용해서 자원 관리를 전담시킬 수 있다.
Lock 클래스는 뮤텍스를 대신 획득해 주고, 필요 없어질 시점에 이를 해제해 준다.
예외 안전성을 갖춘 함수는 아래의 세 가지 보장(guarantee) 중 하나를 제공해야 한다.- 기본적인 보장(basic guarantee)
함수 동작 중에 예외가 발생하면, 실행중인 프로그램에 관련된 모든 것들을 유효한 상태로 유지한다는 보장
(모든 클래스 불변성을 만족)
- 강력한 보장(strong guarantee)
함수 동작 중에 예외가 발생하면, 프로그램의 상태를 절대로 변경하지 않겠다는 보장
(함수에 대하여 원자적(atomic) 동작을 보장)
- 예외 불가 보장(nothrow guarantee)
예외를 절대로 던지지 않겠다는 보장
기본제공 타입(int, 포인터 등)에 대한 모든 연산은 예외를 던지지 않도록 보장되어 있다.
예외 지정 함수예외 지정 함수는 예외 불가 보장 하는 함수가 아닌,
예외가 발생되면 매우 심각한 에러가 생길 수 있어 지정되지 않은 예외가 발생 했을 경우,
std::unexpected 함수를 호출한다.(set_unexpected 함수를 통해 지정 가능)
void f1() throw(int){
throw 0;
throw 0.0;
}
void f2() throw(int){
try{
throw 0.0;
} catch(...){}
}
C++11 이후에는 noexcept(예외 보증 없음)이 추가되었다.
noexcept로 선언해준 함수는 외부에 예외를 던지지 않는다고 지정해준다.
만일 예외 던질 경우, std::terminate 호출
void f1() noexcept;
void f2() noexcept(true);
void f3();
void f4() noexcept(false);
혹은 noexcept를 함수 내에서 질의할 수도 있다.
void f();
void g() noexcept;
int main(void){
noexcept(f());
noexcept(g());
}
가능하다면 예외불가 보장이 가장 이상적이다.
하지만, 예외를 던지는 함수를 호출하지 않고 코드를 구성하기 힘들기 때문에
보통 기본적인 보장과 강력한 보장 중에 하나를 선택해서 사용한다.
강력한 보장, 기본적인 보장
class PrettyMenu{
std::shared_ptr<Image> bgImage;
};
void PrettyMenu::changeBackground(std::istream& imgSrc){
Lock ml(&mutex);
bgImage.reset(new Image(imgSrc));
++imageChanges;
}
위의 코드에서 shared_ptr::reset 함수가 호출되기 위해서는 new가 예외를 던지지 않아야 한다.
shared_ptr을 사용하기 때문에 delete 연산자도 사용자가 호출할 필요가 없다.
하지만, 여전히 Image(imgSrc)에 대하여 생성자 호출 예외의 가능성은 남아 있다.
복사 후 맞바꾸기(copy-and-swap)
객체를 수정하는 경우, 해당 객체의 사본을 만들고 사본을 수정함
(수정 동작 중에 실행되는 연산에서 예외가 발생하여도, 원본 객체는 바뀌지 않고 유지시킬 수 있다.)
필요한 동작을 모두 수행한 뒤, 수정된 객체를 원본 객체와 바꾼다.
예외를 던지지 않는 연산 내부에서 이와 같은 방법을 사용한다.
struct PMImpl{
std::shared_ptr<Image> bgImage;
int imageChanges;
};
class PrettyMenu{
private:
Mutex mutex;
std::shared_ptr<PMImpl> pImpl;
};
void PrettyMenu::changeBackground(std::istream& imgSrc){
using std::swap;
Lock ml(&mutex);
std:shared_ptr<PMImpl> pNew(new PMImpl(*pImpl));
pNew->bgImage.reset(new Image(imgSrc));
++pNew->imageChanges;
swap(pImpl, pNew);
}
하지만, 내부에 사용하는 함수들이 ‘강력한 보장’을 하지 않는다면,
강력한 예외 안전성을 보장하지 못한다.
내부의 함수들이 강력한 예외 안전성을 보장하더라도, 함수가 끝난 이후, 프로그램 상태가 바뀌게 되면,
이 또한 강력한 예외 안전성을 보장하지는 못한다.
void someFunc(){
f1();
f2();
}
함수의 부수 효과(side effect)자기 자신에만 국한된 것들의 상태를 바구면 동작하는 함수의 경우 강력한 보장을 제공하기 상대적으로 쉽다.
하지만, 비지역 데이터에 대해 부수효과를 주는 함수를 포함하는 경우, 강력한 보장을 제공하기 매우 어렵다.
또한 복사 후 맞바꾸기에 소요되는 추가적인 메모리와 복사에 소모되는 연산도 감수해야 한다.